// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包url解析url并实现查询转义。参见RFC 3986。该软件包通常遵循RFC 3986，除非
package url

// 出于兼容性原因，它有所不同。发送更改时，首先
// 搜索旧问题以了解决策的历史。单元测试也应该包含对问题编号的引用和细节。

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// Error报告一个错误以及导致该错误的操作和URL。
type Error struct {
	Op  string
	URL string
	Err error
}

func (e *Error) Unwrap() error { return e.Err }
func (e *Error) Error() string { return fmt.Sprintf("%s %q: %s", e.Op, e.URL, e.Err) }

func (e *Error) Timeout() bool {
	t, ok := e.Err.(interface {
		Timeout() bool
	})
	return ok && t.Timeout()
}

func (e *Error) Temporary() bool {
	t, ok := e.Err.(interface {
		Temporary() bool
	})
	return ok && t.Temporary()
}

const upperhex = "0123456789ABCDEF"

func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}
	return false
}

func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

type encoding int

const (
	encodePath encoding = 1 + iota
	encodePathSegment
	encodeHost
	encodeZone
	encodeUserPassword
	encodeQueryComponent
	encodeFragment
)

type EscapeError string

func (e EscapeError) Error() string {
	return "invalid URL escape " + strconv.Quote(string(e))
}

type InvalidHostError string

func (e InvalidHostError) Error() string {
	return "invalid character " + strconv.Quote(string(e)) + " in host name"
}

// 根据RFC 3986，当
// 出现在URL字符串中时，如果指定的字符应该转义，则返回true。
// 
// 请注意，目前shouldEscape不会正确检查所有
// 保留字符。见戈朗。org/issue/5684。
func shouldEscape(c byte, mode encoding) bool {
	// /§2.3无保留字符（alphanum）
	if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
		return false
	}

	if mode == encodeHost || mode == encodeZone {
		// /§3.2.2主机允许
		// /sub delims=“！”/"$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / “=”
		// 作为注册名称的一部分。
		// 我们添加了：因为我们将：端口作为主机的一部分。
		// 我们添加[]，因为我们将[ipv6]：端口作为主机的一部分。
		// 我们添加了<>因为它们是
		// 我们可能允许的唯一字符，如果我们对它们进行转义，Parse将拒绝它们（因为主机不能对
		// ASCII字节使用%-编码）。
		switch c {
		case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
			return false
		}
	}

	switch c {
	case '-', '_', '.', '~': // /§2.3未保留字符（标记）
		return false

	case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // /§2.2保留字符（保留）
		// URL的不同部分允许一些
		// 保留字符显示为未替换。
		switch mode {
		case encodePath: // /§3.3 
			// RFC允许：@&=+$但保存/，用于将
			// 的含义分配给各个路径段。这个包
			// 只作为一个整体操纵路径，所以我们也允许那些
			// 最后三个。只剩下了吗？逃跑。
			return c == '?'

		case encodePathSegment: // /§3.3 
			// RFC允许：@&=+$但保存/，用于将
			// 的含义分配给各个路径段。
			return c == '/' || c == ';' || c == ',' || c == '?'

		case encodeUserPassword: // /§3.2.1 
			// /RFC允许“；”、“：”、“&”、“=”、“+”、“$”，在
			// userinfo中，和“，”，所以我们必须只转义“@”、“/”和“？”。
			// userinfo的解析将“：”视为特殊，因此我们必须避开
			// 。
			return c == '@' || c == '/' || c == '?' || c == ':'

		case encodeQueryComponent: // /§3.4 
			// RFC保留（因此我们必须逃避）一切。
			return true

		case encodeFragment: // /§4.1 
			// RFC文本是无声的，但语法允许
			// 所有内容，所以不要逃避任何内容。
			return false
		}
	}

	if mode == encodeFragment {
		// RFC 3986§2.2允许不逃离子delims。子文件的一个子集是
		// 包含在RFC 2396§2.2中的保留文件中。剩下的子Delim不
		// 需要转义。为了最大限度地减少潜在的破坏，我们应用了两个限制：
		// （1）我们总是在片段之外转义子delims，以及（2）我们总是
		// 转义单引号，以避免中断之前假设
		// 单引号将被转义的调用方。见第#19917期。
		switch c {
		case '!', '(', ')', '*':
			return false
		}
	}

	// 其他一切都必须逃脱。
	return true
}

// QueryUnescape对QueryEscape进行逆变换，
// 将形式为“%AB”的每个3字节编码的子字符串转换为
// 十六进制解码的字节0xAB。
// 如果任何%后面没有两个十六进制
// 数字，则返回一个错误。
func QueryUnescape(s string) (string, error) {
	return unescape(s, encodeQueryComponent)
}

// PathunScape对PathEscape进行逆变换，
// 将形式为“%AB”的每个3字节编码的子字符串转换为
// 十六进制解码的字节0xAB。如果
// 后面没有跟两个十六进制数字，则返回一个错误。
// 
// PathunScape与QuerynScape相同，只是它没有
// unescape“+”到“”（空格）。
func PathUnescape(s string) (string, error) {
	return unescape(s, encodePathSegment)
}

// unescape unescape一个字符串；模式指定
// URL字符串的哪个部分正在被取消扫描。
func unescape(s string, mode encoding) (string, error) {
	// Count%，检查其格式是否正确。
	n := 0
	hasPlus := false
	for i := 0; i < len(s); {
		switch s[i] {
		case '%':
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]
				if len(s) > 3 {
					s = s[:3]
				}
				return "", EscapeError(s)
			}
			// 在主机组件中每个https:
			// 编码只能用于非ASCII字节。
			// 但是https:
			// 引入了允许%25在IPv6作用域地址文本中转义百分号
			// 的功能。耶。
			if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" {
				return "", EscapeError(s[i : i+3])
			}
			if mode == encodeZone {
				// RFC 6874基本上表示区域标识符“一切正常”，甚至非ASCII也可以被冗余转义，
				// 但在这里，将%-转义字节限制为那些
				// 以未转义形式表示的有效主机名字节似乎是谨慎的。
				// 也就是说，可以在区域标识符中使用转义，但不能使用
				// 来引入不能直接写入的字节。
				// 但Windows在这里放置了空格！耶。
				v := unhex(s[i+1])<<4 | unhex(s[i+2])
				if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) {
					return "", EscapeError(s[i : i+3])
				}
			}
			i += 3
		case '+':
			hasPlus = mode == encodeQueryComponent
			i++
		default:
			if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
				return "", InvalidHostError(s[i : i+1])
			}
			i++
		}
	}

	if n == 0 && !hasPlus {
		return s, nil
	}

	var t strings.Builder
	t.Grow(len(s) - 2*n)
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '%':
			t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
			i += 2
		case '+':
			if mode == encodeQueryComponent {
				t.WriteByte(' ')
			} else {
				t.WriteByte('+')
			}
		default:
			t.WriteByte(s[i])
		}
	}
	return t.String(), nil
}

// queryscape将该字符串转义，以便将其安全地放置在URL查询中。
func QueryEscape(s string) string {
	return escape(s, encodeQueryComponent)
}

// PathEscape对字符串进行转义，以便将其安全地放置在URL路径段中，
// 根据需要将特殊字符（包括/）替换为%XX序列。
func PathEscape(s string) string {
	return escape(s, encodePathSegment)
}

func escape(s string, mode encoding) string {
	spaceCount, hexCount := 0, 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if shouldEscape(c, mode) {
			if c == ' ' && mode == encodeQueryComponent {
				spaceCount++
			} else {
				hexCount++
			}
		}
	}

	if spaceCount == 0 && hexCount == 0 {
		return s
	}

	var buf [64]byte
	var t []byte

	required := len(s) + 2*hexCount
	if required <= len(buf) {
		t = buf[:required]
	} else {
		t = make([]byte, required)
	}

	if hexCount == 0 {
		copy(t, s)
		for i := 0; i < len(s); i++ {
			if s[i] == ' ' {
				t[i] = '+'
			}
		}
		return string(t)
	}

	j := 0
	for i := 0; i < len(s); i++ {
		switch c := s[i]; {
		case c == ' ' && mode == encodeQueryComponent:
			t[j] = '+'
			j++
		case shouldEscape(c, mode):
			t[j] = '%'
			t[j+1] = upperhex[c>>4]
			t[j+2] = upperhex[c&15]
			j += 3
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

// 一个URL代表一个解析过的URL（从技术上讲，是一个URI引用）。
// 
// 表示的一般形式是：
// 
// /[scheme:[/[userinfo@]host][/]path[？query][query][片段]
// 
// 在方案后不以斜杠开头的URL被解释为：
// 结果是，无法判断路径中的哪些斜杠是原始URL中的
// 斜杠，哪些是%2f。这种区别很少重要，
// 但当它重要时，代码应该使用RawPath，这是一个可选字段，只有在默认编码与Path不同时，才会设置
// 。
// 
// URL的String方法使用EscapedPath方法获取路径。有关更多详细信息，请参阅
// EscapedPath方法。
type URL struct {
	Scheme      string
	Opaque      string    // 编码的不透明数据
	User        *Userinfo // 用户名和密码信息
	Host        string    // 主机或主机：端口
	Path        string    // 路径（相对路径可以省略前导斜杠）
	RawPath     string    // 编码的路径提示（请参阅转义路径方法）
	ForceQuery  bool      // 附加查询（“？”），即使RawQuery为空
	RawQuery    string    // 编码的查询值，不带“？”
	Fragment    string    // 用于引用的片段，不带“#”
	RawFragment string    // 编码的片段提示（请参见EscapedFragment方法）
}

// 用户返回包含提供的用户名
// 且未设置密码的用户信息。
func User(username string) *Userinfo {
	return &Userinfo{username, "", false}
}

// UserPassword返回一个用户信息，其中包含提供的用户名
// 和密码。
// 
// 此功能只能用于旧网站。
// RFC 2396警告说，不建议以这种方式解释用户信息，因为在几乎所有使用过的情况下，通过身份验证
// 明文信息（如URI）都被证明是一种安全风险。”
func UserPassword(username, password string) *Userinfo {
	return &Userinfo{username, password, true}
}

// Userinfo类型是用户名和
// URL密码详细信息的不可变封装。现有的Userinfo值保证
// 有一个用户名集（RFC 2396允许可能为空）、
// 和一个可选的密码。
type Userinfo struct {
	username    string
	password    string
	passwordSet bool
}

// Username返回用户名。
func (u *Userinfo) Username() string {
	if u == nil {
		return ""
	}
	return u.username
}

// Password返回密码，以防密码被设置，以及密码是否被设置。
func (u *Userinfo) Password() (string, bool) {
	if u == nil {
		return "", false
	}
	return u.password, u.passwordSet
}

// String以“username[：password]”的标准格式返回编码的用户信息。
func (u *Userinfo) String() string {
	if u == nil {
		return ""
	}
	s := escape(u.username, encodeUserPassword)
	if u.passwordSet {
		s += ":" + escape(u.password, encodeUserPassword)
	}
	return s
}

// 也许rawURL的形式是scheme:path。
// /（方案必须为[a-zA-Z][a-zA-Z0-9+-.]*）
// 如果是，返回方案，路径；否则返回“”，rawURL。
func getScheme(rawURL string) (scheme, path string, err error) {
	for i := 0; i < len(rawURL); i++ {
		c := rawURL[i]
		switch {
		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
		// 什么都不做
		case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
			if i == 0 {
				return "", rawURL, nil
			}
		case c == ':':
			if i == 0 {
				return "", "", errors.New("missing protocol scheme")
			}
			return rawURL[:i], rawURL[i+1:], nil
		default:
			// 我们遇到了一个无效字符，
			// 因此没有有效的方案
			return "", rawURL, nil
		}
	}
	return "", rawURL, nil
}

// Parse将原始url解析为url结构。
// 
// url可以是相对的（路径，没有主机）或绝对的
// （从一个方案开始）。尝试在没有方案的情况下解析主机名和路径
// 无效，但由于解析不明确，可能不一定返回
// 错误。
func Parse(rawURL string) (*URL, error) {
	// Cut off#frag 
	u, frag, _ := strings.Cut(rawURL, "#")
	url, err := parse(u, false)
	if err != nil {
		return nil, &Error{"parse", u, err}
	}
	if frag == "" {
		return url, nil
	}
	if err = url.setFragment(frag); err != nil {
		return nil, &Error{"parse", rawURL, err}
	}
	return url, nil
}

// ParseRequestURI将原始url解析为url结构。它假定在HTTP请求中收到了
// url，因此该url仅被解释为绝对URI或绝对路径。
// 假定字符串url没有#片段后缀。
// （在将URL发送到Web服务器之前，Web浏览器会剥离#片段。）
func ParseRequestURI(rawURL string) (*URL, error) {
	url, err := parse(rawURL, true)
	if err != nil {
		return nil, &Error{"parse", rawURL, err}
	}
	return url, nil
}

// parse在两种上下文之一中从字符串解析URL。如果
// viaRequest为true，则假定URL通过HTTP请求到达，在这种情况下，只允许绝对URL或路径绝对相对URL。
// 如果viaRequest为false，则允许使用所有形式的相对URL。
func parse(rawURL string, viaRequest bool) (*URL, error) {
	var rest string
	var err error

	if stringContainsCTLByte(rawURL) {
		return nil, errors.New("net/url: invalid control character in URL")
	}

	if rawURL == "" && viaRequest {
		return nil, errors.New("empty url")
	}
	url := new(URL)

	if rawURL == "*" {
		url.Path = "*"
		return url, nil
	}

	// 拆分可能的前导字符“http:”、“mailto:”等。
	// 不能包含转义字符。ABCFDG＊＊＊＊＊＊＊/我们认为每个RFC 3986的无根路径不透明。
	if url.Scheme, rest, err = getScheme(rawURL); err != nil {
		return nil, err
	}
	url.Scheme = strings.ToLower(url.Scheme)

	if strings.HasSuffix(rest, "?") && strings.Count(rest, "?") == 1 {
		url.ForceQuery = true
		rest = rest[:len(rest)-1]
	} else {
		rest, url.RawQuery, _ = strings.Cut(rest, "?")
	}

	if !strings.HasPrefix(rest, "/") {
		if url.Scheme != "" {
			url.Opaque = rest
			return url, nil
		}
		if viaRequest {
			return nil, errors.New("invalid URI for request")
		}

		// 避免与格式错误的方案混淆，例如cache_object:foo/bar。
		// 见golang。org/issue/16822。
		// 
		// RFC 3986，§3.3:
		// 此外，URI引用（第4.1节）可能是相对路径引用，
		// 在这种情况下，第一个路径段不能包含冒号（“：”）字符。
		if segment, _, _ := strings.Cut(rest, "/"); strings.Contains(segment, ":") {
			// 第一个路径段有冒号。在相对URL中不允许。
			return nil, errors.New("first path segment in URL cannot contain colon")
		}
	}

	if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "// /”）和&strings.HasPrefix（rest，“
		var authority string
		authority, rest = rest[2:], ""
		if i := strings.Index(authority, "/"); i >= 0 {
			authority, rest = authority[:i], authority[i:]
		}
		url.User, url.Host, err = parseAuthority(authority)
		if err != nil {
			return nil, err
		}
	}
	// 设置路径，也可以设置RawPath。
	// RawPath是路径编码的提示。如果
	// 路径的默认转义是等效的，我们不想设置它，以帮助确保人们一般不依赖它。
	if err := url.setPath(rest); err != nil {
		return nil, err
	}
	return url, nil
}

func parseAuthority(authority string) (user *Userinfo, host string, err error) {
	i := strings.LastIndex(authority, "@")
	if i < 0 {
		host, err = parseHost(authority)
	} else {
		host, err = parseHost(authority[i+1:])
	}
	if err != nil {
		return nil, "", err
	}
	if i < 0 {
		return nil, host, nil
	}
	userinfo := authority[:i]
	if !validUserinfo(userinfo) {
		return nil, "", errors.New("net/url: invalid userinfo")
	}
	if !strings.Contains(userinfo, ":") {
		if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
			return nil, "", err
		}
		user = User(userinfo)
	} else {
		username, password, _ := strings.Cut(userinfo, ":")
		if username, err = unescape(username, encodeUserPassword); err != nil {
			return nil, "", err
		}
		if password, err = unescape(password, encodeUserPassword); err != nil {
			return nil, "", err
		}
		user = UserPassword(username, password)
	}
	return user, host, nil
}

// 解析主机将主机解析为一个权限，而不包含用户
// 信息。也就是说，作为主机[：端口]。
func parseHost(host string) (string, error) {
	if strings.HasPrefix(host, "[") {
		// 解析RFC 3986和RFC 6874中的IP文本。
		// 例如，“[fe80:：1]”，“[fe80:：1%25en0]”，“[fe80:：1]：80”。
		i := strings.LastIndex(host, "]")
		if i < 0 {
			return "", errors.New("missing ']' in host")
		}
		colonPort := host[i+1:]
		if !validOptionalPort(colonPort) {
			return "", fmt.Errorf("invalid port %q after host", colonPort)
		}

		// RFC 6874定义了%25（%-encoded percent）引入了
		// 区域标识符，区域标识符基本上可以使用
		// 任何它喜欢的编码。这与主机不同，主机
		// 只能对非ASCII字节进行%编码。
		// 我们确实对该区域施加了一些限制，以避免愚蠢的行为。
		zone := strings.Index(host[:i], "%25")
		if zone >= 0 {
			host1, err := unescape(host[:zone], encodeHost)
			if err != nil {
				return "", err
			}
			host2, err := unescape(host[zone:i], encodeZone)
			if err != nil {
				return "", err
			}
			host3, err := unescape(host[i:], encodeHost)
			if err != nil {
				return "", err
			}
			return host1 + host2 + host3, nil
		}
	} else if i := strings.LastIndex(host, ":"); i != -1 {
		colonPort := host[i:]
		if !validOptionalPort(colonPort) {
			return "", fmt.Errorf("invalid port %q after host", colonPort)
		}
	}

	var err error
	if host, err = unescape(host, encodeHost); err != nil {
		return "", err
	}
	return host, nil
}

// setPath根据提供的
// 转义路径p设置URL的路径和RawPath字段。它保持不变，即当RawPath与路径的默认编码不同时，仅指定
// 。
// 例如：
// /-setPath（/foo/bar）将设置Path=“/foo/bar”和RawPath=”“
// /-setPath（/foo%2fbar”）将设置Path=“/foo/bar”和RawPath=“/foo%2fbar”
// 仅当提供的路径包含无效的
// 转义时，setPath才会返回错误。
func (u *URL) setPath(p string) error {
	path, err := unescape(p, encodePath)
	if err != nil {
		return err
	}
	u.Path = path
	if escp := escape(path, encodePath); p == escp {
		// 默认编码可以。
		u.RawPath = ""
	} else {
		u.RawPath = p
	}
	return nil
}

// EscapedPath返回u.Path的转义形式。
// 一般来说，任何路径都有多种可能的转义形式。
// EscapedPath是u.Path的有效转义时返回u.RawPath。
// 否则，EscapedPath将忽略u.RawPath并自行计算转义的
// 表单。
// String和RequestURI方法使用EscapedPath来构造
// 它们的结果。
// 通常，代码应该调用EscapedPath，而不是直接读取u.RawPath。
func (u *URL) EscapedPath() string {
	if u.RawPath != "" && validEncoded(u.RawPath, encodePath) {
		p, err := unescape(u.RawPath, encodePath)
		if err == nil && p == u.Path {
			return u.RawPath
		}
	}
	if u.Path == "*" {
		return "*" // 不转义（问题11202）
	}
	return escape(u.Path, encodePath)
}

// 有效编码报告s是否是有效的编码路径或片段，
// 根据模式。
// 它不能包含编码期间需要转义的任何字节。
func validEncoded(s string, mode encoding) bool {
	for i := 0; i < len(s); i++ {
		// RFC 3986，附录A。
		// pchar=unreserved/pct-encoded/sub-delims/“：“/”@”。
		// shouldEscape不太符合RFC，
		// 所以我们自己检查子文件，让
		// shouldEscape处理其他文件。
		switch s[i] {
		case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
			// ok 
		case '[', ']':
			// ok-未在RFC 3986中指定，但被现代浏览器忽略
		case '%':
			// ok-百分比编码，将解码
		default:
			if shouldEscape(s[i], mode) {
				return false
			}
		}
	}
	return true
}

// setFragment与setPath类似，但用于Fragment/RawFragment。
func (u *URL) setFragment(f string) error {
	frag, err := unescape(f, encodeFragment)
	if err != nil {
		return err
	}
	u.Fragment = frag
	if escf := escape(frag, encodeFragment); f == escf {
		// 默认编码可以。
		u.RawFragment = ""
	} else {
		u.RawFragment = f
	}
	return nil
}

// EscapedFragment返回u.Fragment的转义形式。
// 一般来说，任何片段都有多种可能的转义形式。
// 当u.Fragment的转义有效时，EscapedFragment返回u.RawFragment。
// 否则，EscapedFragment将忽略u.RawFragment并自行计算一个转义的
// 表单。
// String方法使用EscapedFragment构造其结果。
// 通常，代码应该调用EscapedFragment，而不是直接读取u.RawFragment。
func (u *URL) EscapedFragment() string {
	if u.RawFragment != "" && validEncoded(u.RawFragment, encodeFragment) {
		f, err := unescape(u.RawFragment, encodeFragment)
		if err == nil && f == u.Fragment {
			return u.RawFragment
		}
	}
	return escape(u.Fragment, encodeFragment)
}

// validOptionalPort报告端口是否为空字符串
// 或匹配
func validOptionalPort(port string) bool {
	if port == "" {
		return true
	}
	if port[0] != ':' {
		return false
	}
	for _, b := range port[1:] {
		if b < '0' || b > '9' {
			return false
		}
	}
	return true
}

// 字符串将URL重新组合为有效的URL字符串。
// 结果的一般形式是：
// 
// 方案：不透明？查询#片段
// scheme:
// 
// 如果u.不透明为非空，字符串使用第一种形式；
// 否则使用第二种形式。
// 主机中的所有非ASCII字符都被转义。
// 要获取路径，String使用u.EscapedPath（）。
// 
// 在第二种形式中，以下规则适用：
// -如果u.Scheme为空，则省略Scheme:。
// /-如果u.User为零，则忽略userinfo@。
// /-如果u.Host为空，则省略Host/。
// -如果u.Scheme和u.Host为空，而u.User为零，则
// /整个scheme:
// -如果u.Host非空且u.Path以
// Host/Path表单不会添加自己的/。
// /-如果u.RawQuery为空？省略查询。
// /-如果u.Fragment为空，#Fragment被省略。
func (u *URL) String() string {
	var buf strings.Builder
	if u.Scheme != "" {
		buf.WriteString(u.Scheme)
		buf.WriteByte(':')
	}
	if u.Opaque != "" {
		buf.WriteString(u.Opaque)
	} else {
		if u.Scheme != "" || u.Host != "" || u.User != nil {
			if u.Host != "" || u.Path != "" || u.User != nil {
				buf.WriteString("// “）
			}
			if ui := u.User; ui != nil {
				buf.WriteString(ui.String())
				buf.WriteByte('@')
			}
			if h := u.Host; h != "" {
				buf.WriteString(escape(h, encodeHost))
			}
		}
		path := u.EscapedPath()
		if path != "" && path[0] != '/' && u.Host != "" {
			buf.WriteByte('/')
		}
		if buf.Len() == 0 {
			// /RFC 3986§4.2 
			// 包含冒号字符（例如，“this:that”）的路径段
			// 不能用作相对路径引用的第一段，因为
			// 它会被误认为是方案名称。这样的一段必须是
			// 前面有一个点段（例如“/this:that”）创建一个相对的-
			// 路径引用。
			if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
				buf.WriteString("./")
			}
		}
		buf.WriteString(path)
	}
	if u.ForceQuery || u.RawQuery != "" {
		buf.WriteByte('?')
		buf.WriteString(u.RawQuery)
	}
	if u.Fragment != "" {
		buf.WriteByte('#')
		buf.WriteString(u.EscapedFragment())
	}
	return buf.String()
}

// 编辑后的密码类似于字符串，但会将任何密码替换为“xxxxx”。
// 只对u.URL中的密码进行了编辑。
func (u *URL) Redacted() string {
	if u == nil {
		return ""
	}

	ru := *u
	if _, has := ru.User.Password(); has {
		ru.User = UserPassword(ru.User.Username(), "xxxxx")
	}
	return ru.String()
}

// 值将字符串键映射到值列表。
// 通常用于查询参数和表单值。
// 与http不同。Header map，值映射中的键
// 区分大小写。
type Values map[string][]string

// Get获取与给定键关联的第一个值。
// 如果没有与键关联的值，Get将返回
// 空字符串。要访问多个值，请直接使用映射
// 。
func (v Values) Get(key string) string {
	if v == nil {
		return ""
	}
	vs := v[key]
	if len(vs) == 0 {
		return ""
	}
	return vs[0]
}

// Set将键设置为value。它将替换任何现有的
// 值。
func (v Values) Set(key, value string) {
	v[key] = []string{value}
}

// Add将值添加到key。它附加到与键相关的任何现有
// 值。
func (v Values) Add(key, value string) {
	v[key] = append(v[key], value)
}

// Del删除与键关联的值。
func (v Values) Del(key string) {
	delete(v, key)
}

// Has检查是否设置了给定的密钥。
func (v Values) Has(key string) bool {
	_, ok := v[key]
	return ok
}

// ParseQuery解析URL编码的查询字符串，并返回一个映射，列出为每个键指定的值。
// ParseQuery始终返回一个包含所有
// 找到的有效查询参数的非零映射；err描述遇到的第一个解码错误
// 如果有。
// 
// 查询应该是一个用符号分隔的key=value设置列表。
// 没有等号的设置被解释为一个空
// 值的密钥集。
// 包含非URL编码分号的设置被视为无效。
func ParseQuery(query string) (Values, error) {
	m := make(Values)
	err := parseQuery(m, query)
	return m, err
}

func parseQuery(m Values, query string) (err error) {
	for query != "" {
		var key string
		key, query, _ = strings.Cut(query, "&")
		if strings.Contains(key, ";") {
			err = fmt.Errorf("invalid semicolon separator in query")
			continue
		}
		if key == "" {
			continue
		}
		key, value, _ := strings.Cut(key, "=")
		key, err1 := QueryUnescape(key)
		if err1 != nil {
			if err == nil {
				err = err1
			}
			continue
		}
		value, err1 = QueryUnescape(value)
		if err1 != nil {
			if err == nil {
				err = err1
			}
			continue
		}
		m[key] = append(m[key], value)
	}
	return err
}

// Encode将值编码为“URL编码”形式
// （“bar=baz&foo=qux”），按键排序。
func (v Values) Encode() string {
	if v == nil {
		return ""
	}
	var buf strings.Builder
	keys := make([]string, 0, len(v))
	for k := range v {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		vs := v[k]
		keyEscaped := QueryEscape(k)
		for _, v := range vs {
			if buf.Len() > 0 {
				buf.WriteByte('&')
			}
			buf.WriteString(keyEscaped)
			buf.WriteByte('=')
			buf.WriteString(QueryEscape(v))
		}
	}
	return buf.String()
}

// 根据RFC 3986，resolvePath应用参考文献中的特殊路径段，并将其应用于基础。
func resolvePath(base, ref string) string {
	var full string
	if ref == "" {
		full = base
	} else if ref[0] != '/' {
		i := strings.LastIndex(base, "/")
		full = base[:i+1] + ref
	} else {
		full = ref
	}
	if full == "" {
		return ""
	}

	var (
		elem string
		dst  strings.Builder
	)
	first := true
	remaining := full
	// 我们想返回一个前导“/”，现在就写吧。
	dst.WriteByte('/')
	found := true
	for found {
		elem, remaining, found = strings.Cut(remaining, "/")
		if elem == "." {
			first = false
			// 删除
			continue
		}

		if elem == ".." {
			// 忽略前面的“/”我们已经写过了。
			str := dst.String()[1:]
			index := strings.LastIndexByte(str, '/')

			dst.Reset()
			dst.WriteByte('/')
			if index == -1 {
				first = true
			} else {
				dst.WriteString(str[:index])
			}
		} else {
			if !first {
				dst.WriteByte('/')
			}
			dst.WriteString(elem)
			first = false
		}
	}

	if elem == "." || elem == ".." {
		dst.WriteByte('/')
	}

	// 我们写了一个首字母“/”，但我们不想要两个。
	r := dst.String()
	if len(r) > 1 && r[1] == '/' {
		r = r[1:]
	}
	return r
}

// isbs报告URL是否为绝对URL。
// 绝对意味着它有一个非空的方案。
func (u *URL) IsAbs() bool {
	return u.Scheme != ""
}

// Parse在接收方的上下文中解析URL。提供的URL 
// 可以是相对的，也可以是绝对的。Parse返回nil，err on Parse 
// 失败，否则返回值与resolverence相同。
func (u *URL) Parse(ref string) (*URL, error) {
	refURL, err := Parse(ref)
	if err != nil {
		return nil, err
	}
	return u.ResolveReference(refURL), nil
}

// 根据RFC 3986第5.2节，ResolveReference将URI引用从
// 解析为绝对基URI u。URI引用
// 可以是相对的，也可以是绝对的。ResolveReference始终返回一个新的
// URL实例，即使返回的URL与
// 基或引用相同。如果ref是绝对URL，则ResolveReference 
// 忽略base并返回ref.
func (u *URL) ResolveReference(ref *URL) *URL {
	url := *ref
	if ref.Scheme == "" {
		url.Scheme = u.Scheme
	}
	if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
		// 的副本，即“绝对URI”或“net_path”大小写。
		// 我们可以忽略setPath中的错误，因为我们知道我们提供了一个有效的转义路径。
		url.setPath(resolvePath(ref.EscapedPath(), ""))
		return &url
	}
	if ref.Opaque != "" {
		url.User = nil
		url.Host = ""
		url.Path = ""
		return &url
	}
	if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
		url.RawQuery = u.RawQuery
		if ref.Fragment == "" {
			url.Fragment = u.Fragment
			url.RawFragment = u.RawFragment
		}
	}
	// “abs_路径”或“rel_路径”案例。
	url.Host = u.Host
	url.User = u.User
	url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
	return &url
}

// Query解析RawQuery并返回相应的值。
// 它会自动丢弃格式错误的值对。
// 要检查错误，请使用ParseQuery。
func (u *URL) Query() Values {
	v, _ := ParseQuery(u.RawQuery)
	return v
}

// RequestURI返回编码路径？质疑还是不透明？查询
// 将在HTTP请求中用于u的字符串。
func (u *URL) RequestURI() string {
	result := u.Opaque
	if result == "" {
		result = u.EscapedPath()
		if result == "" {
			result = "/"
		}
	} else {
		if strings.HasPrefix(result, "// “{
			result = u.Scheme + ":" + result
		}
	}
	if u.ForceQuery || u.RawQuery != "" {
		result += "?" + u.RawQuery
	}
	return result
}

// 主机名返回u.主机，如果存在，则删除所有有效端口号。
// 
// 如果结果用方括号括起来，就像字面上的IPv6地址一样，
// 将从结果中删除方括号。
func (u *URL) Hostname() string {
	host, _ := splitHostPort(u.Host)
	return host
}

// Port返回u.Host的端口部分，不带前导冒号。
// 
// 如果u主机不包含有效的数字端口，端口将返回空字符串。
func (u *URL) Port() string {
	_, port := splitHostPort(u.Host)
	return port
}

// splitHostPort将主机和端口分开。如果端口无效，则返回
// 作为主机的整个输入，并且不检查主机的有效性。
// 与net不同。SplitHostPort，但根据RFC 3986，它要求端口为数字。
func splitHostPort(hostPort string) (host, port string) {
	host = hostPort

	colon := strings.LastIndexByte(host, ':')
	if colon != -1 && validOptionalPort(host[colon:]) {
		host, port = host[:colon], host[colon+1:]
	}

	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
		host = host[1 : len(host)-1]
	}

	return
}

// 封送接口实现。
// 希望实现MarshalText/UnmarshalText，但这将改变URL的JSON表示形式。

func (u *URL) MarshalBinary() (text []byte, err error) {
	return []byte(u.String()), nil
}

func (u *URL) UnmarshalBinary(text []byte) error {
	u1, err := Parse(string(text))
	if err != nil {
		return err
	}
	*u = *u1
	return nil
}

// validUserinfo根据RFC 3986报告s是否为有效的用户信息字符串
// 3.2.1节：
// userinfo=*（未保留/pct编码/sub delims/“：”）。
// unreserved=ALPHA/DIGIT/“-”/”“”/“~”
// sub delims=“！”/“$”/“&“/”/”（“/”）
// “*”/“+”/“，“/”；”/“=”
// 
// 它不验证pct编码。调用者通过func-unescape实现这一点。
func validUserinfo(s string) bool {
	for _, r := range s {
		if 'A' <= r && r <= 'Z' {
			continue
		}
		if 'a' <= r && r <= 'z' {
			continue
		}
		if '0' <= r && r <= '9' {
			continue
		}
		switch r {
		case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
			'(', ')', '*', '+', ',', ';', '=', '%', '@':
			continue
		default:
			return false
		}
	}
	return true
}

// stringContainsCTLByte报告s是否包含任何ASCII控制字符。
func stringContainsCTLByte(s string) bool {
	for i := 0; i < len(s); i++ {
		b := s[i]
		if b < ' ' || b == 0x7f {
			return true
		}
	}
	return false
}
